/*
 *  Copyright (c) 2020 Arthur Milchior <arthur@milchior.fr>
 *
 *  This program is free software; you can redistribute it and/or modify it under
 *  the terms of the GNU General Public License as published by the Free Software
 *  Foundation; either version 3 of the License, or (at your option) any later
 *  version.
 *
 *  This program is distributed in the hope that it will be useful, but WITHOUT ANY
 *  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 *  PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License along with
 *  this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.ichi2.anki.libanki

import android.annotation.SuppressLint
import anki.scheduler.CardAnswer.Rating
import com.ichi2.anki.libanki.exception.ConfirmModSchemaException
import com.ichi2.anki.libanki.testutils.InMemoryAnkiTest
import com.ichi2.anki.libanki.testutils.ext.addNote
import com.ichi2.anki.libanki.testutils.ext.newNote
import net.ankiweb.rsdroid.exceptions.BackendInvalidInputException
import org.hamcrest.MatcherAssert.assertThat
import org.hamcrest.Matchers.containsString
import org.hamcrest.Matchers.equalTo
import org.hamcrest.Matchers.hasItemInArray
import org.hamcrest.Matchers.not
import org.junit.Assert.assertEquals
import org.junit.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertNotNull

class CardTest : InMemoryAnkiTest() {
    @Test
    fun test_toString() {
        val output = addBasicNote().firstCard().toString()

        assertThat(output, not(containsString("Companion")))
        assertThat(output, not(containsString("SKIP_PRINT")))

        assertThat(output, not(containsString("note")))
        assertThat(output, not(containsString("renderOutput")))
        assertThat(output, not(containsString("col")))
        assertThat(output, not(containsString("timerStarted")))
    }

    /******************
     ** autogenerated from https://github.com/ankitects/anki/blob/2c73dcb2e547c44d9e02c20a00f3c52419dc277b/pylib/tests/test_cards.py*
     ******************/
    @Test
    fun test_delete() {
        val note = col.newNote()
        note.setItem("Front", "1")
        note.setItem("Back", "2")
        col.addNote(note)
        val cid = note.cards()[0].id
        col.sched.answerCard(col.sched.card!!, Rating.HARD)
        col.removeCardsAndOrphanedNotes(listOf(cid))
        assertEquals(0, col.cardCount())
        assertEquals(0, col.noteCount())
        assertEquals(0, col.db.queryScalar("select count() from notes"))
        assertEquals(0, col.db.queryScalar("select count() from cards"))
        assertEquals(2, col.db.queryScalar("select count() from graves"))
    }

    @Test
    @SuppressLint("CheckResult") // col.models.current()!!.id
    fun test_misc_cards() {
        val note = col.newNote()
        note.setItem("Front", "1")
        note.setItem("Back", "2")
        col.addNote(note)
        val c = note.cards()[0]
        col.notetypes.current().id
        assertEquals(0, c.template().ord)
    }

    @Test
    fun test_genrem() {
        val note = col.newNote()
        note.setItem("Front", "1")
        note.setItem("Back", "")
        col.addNote(note)
        assertEquals(1, note.numberOfCards())
        val noteType = col.notetypes.current()
        // adding a new template should automatically create cards
        var t =
            Notetypes.newTemplate("rev").apply {
                qfmt = "{{Front}}1"
                afmt = ""
            }
        col.notetypes.addTemplateModChanged(noteType, t)
        col.notetypes.save(noteType)
        assertEquals(2, note.numberOfCards())
        // if the template is changed to remove cards, they'll be removed
        t =
            noteType.templates[1].apply {
                qfmt = "{{Back}}"
            }
        col.notetypes.save(noteType)
        val rep = col.getEmptyCards().emptyCids()
        col.removeCardsAndOrphanedNotes(rep)
        assertEquals(1, note.numberOfCards())
        // if we add to the note, a card should be automatically generated
        note.load()
        note.setItem("Back", "1")
        note.flush()
        assertEquals(2, note.numberOfCards())
    }

    @Test
    fun test_gendeck() {
        val cloze = col.notetypes.byName("Cloze")
        col.notetypes.setCurrent(cloze!!)
        val note = col.newNote()
        note.setItem("Text", "{{c1::one}}")
        col.addNote(note)
        assertEquals(1, col.cardCount())
        assertEquals(1, note.cards()[0].did)
        // set the note type to a new default col
        val newId = addDeck("new")
        cloze.did = newId
        col.notetypes.save(cloze)
        // a newly generated card should share the first card's col
        note.setItem("Text", "{{c2::two}}")
        note.flush()
        assertEquals(1, note.cards()[1].did)
        // and same with multiple cards
        note.setItem("Text", "{{c3::three}}")
        note.flush()
        assertEquals(1, note.cards()[2].did)
    }

    @Test
    @Throws(ConfirmModSchemaException::class)
    fun test_gen_or() {
        val noteType = col.notetypes.byName("Basic")
        assertNotNull(noteType)
        col.notetypes.renameFieldLegacy(noteType, noteType.fields[0], "A")
        col.notetypes.renameFieldLegacy(noteType, noteType.fields[1], "B")
        val fld2 = col.notetypes.newField("C")
        fld2.setOrd(null)
        col.notetypes.addFieldLegacy(noteType, fld2)
        noteType.templates[0].qfmt = "{{A}}{{B}}{{C}}"
        // ensure first card is always generated,
        // because at last one card is generated
        val tmpl = Notetypes.newTemplate("AND_OR")
        tmpl.qfmt = "        {{A}}    {{#B}}        {{#C}}            {{B}}        {{/C}}    {{/B}}"
        col.notetypes.addTemplate(noteType, tmpl)
        col.notetypes.save(noteType)
        col.notetypes.setCurrent(noteType)
        var note = col.newNote()
        note.setItem("A", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0, 1))
        note = col.newNote()
        note.setItem("B", "foo")
        note.setItem("C", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0, 1))
        note = col.newNote()
        note.setItem("B", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0))
        note = col.newNote()
        note.setItem("C", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0))
        note = col.newNote()
        note.setItem("A", "foo")
        note.setItem("B", "foo")
        note.setItem("C", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0, 1))
        note = col.newNote()
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0))
        // First card is generated if no other card
    }

    @Test
    @Throws(ConfirmModSchemaException::class)
    fun test_gen_not() {
        val noteType = col.notetypes.byName("Basic")
        assertNotNull(noteType)
        col.notetypes.renameFieldLegacy(noteType, noteType.fields[0], "First")
        col.notetypes.renameFieldLegacy(noteType, noteType.fields[1], "Front")
        val fld2 = col.notetypes.newField("AddIfEmpty")
        fld2.name = "AddIfEmpty"
        col.notetypes.addFieldLegacy(noteType, fld2)

        // ensure first card is always generated,
        // because at last one card is generated
        noteType.templates[0].qfmt = "{{AddIfEmpty}}{{Front}}{{First}}"
        val tmpl = Notetypes.newTemplate("NOT")
        tmpl.qfmt = "    {{^AddIfEmpty}}        {{Front}}    {{/AddIfEmpty}}    "
        col.notetypes.addTemplate(noteType, tmpl)
        col.notetypes.save(noteType)
        col.notetypes.setCurrent(noteType)
        var note = col.newNote()
        note.setItem("First", "foo")
        note.setItem("AddIfEmpty", "foo")
        note.setItem("Front", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0))
        note = col.newNote()
        note.setItem("First", "foo")
        note.setItem("AddIfEmpty", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0))
        note = col.newNote()
        note.setItem("First", "foo") // ensure first note generated
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0))
        note = col.newNote()
        note.setItem("First", "foo")
        note.setItem("Front", "foo")
        col.addNote(note)
        assertNoteOrdinalAre(note, arrayOf(0, 1))
    }

    @Test
    fun `renderOutput regression test`() {
        // #18896: renderOutput throws "missing template". We want to be sure this error does not change
        // as we ignore it when reporting it in ACRA
        val cid = addBasicNote().firstCard().id

        // corrupt the card
        col.db.execute("update cards set ord = 2 where id = ?", cid)

        // at the time of writing, "missing template" is a hardcoded string. permalink:
        // https://github.com/ankitects/anki/blob/71ec878780c1b81b49b1e18b3c41237bda51e20c/rslib/src/notetype/render.rs#L54-L64
        val ex = assertFailsWith<BackendInvalidInputException> { col.getCard(cid).renderOutput(col) }
        assertThat(ex.message, equalTo("missing template"))
    }

    private fun assertNoteOrdinalAre(
        note: Note,
        ords: Array<Int>,
    ) {
        val cards = note.cards()
        assumeThat(cards.size, equalTo(ords.size))
        for (card in cards) {
            val ord = card.ord
            assumeThat(ords, hasItemInArray(ord))
        }
    }
}
